import math
import datetime
from itertools import groupby
from markupsafe import Markup

from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_is_zero, float_compare, float_round


class PlanMaterialRequirementWizard(models.TransientModel):
    _name = 'plan.material.requirement.wizard'
    _description = 'Plan Material Requirement Wizard'  # 计划投料向导

    sale_order_line = fields.Many2one('sale.order.line', 'Sales Order Line', readonly=False)
    sale_order_line_ids_str = fields.Char()
    sale_order_line_ids = fields.One2many('sale.order.line', readonly=False, compute='_compute_sale_ids')

    development_id = fields.Many2one('s.research.and.development.order.line')
    development_ids_str = fields.Char()
    development_ids = fields.One2many('s.research.and.development.order.line', readonly=False, compute='_compute_development_ids')

    product_id = fields.Many2one('product.product', 'Product')
    bom_id = fields.Many2one('mrp.bom')
    # historical_yield = fields.Float('Historical Yield', compute='_compute_historical_yield')
    bom_line_ids = fields.One2many('requirement.bom.line.wizard', 'plan_id', 'BOM Lines')
    opt_lot_size = fields.Integer(string='Optimal Lot Size', default=72)  # 客户要求默认设定72
    scrap_rate = fields.Float(string='Scrap Rate')

    base_mo = fields.Char('Base Mo', copy=False, readonly=True)
    procurement_group_id = fields.Many2one('procurement.group', copy=False, readonly=True)

    utilization_rate = fields.Float(string='Utilization rate', readonly=True)  # 利用率
    remaining_planned_quantity = fields.Float('Remaining planned quantity', readonly=True)
    actual_planned_quantity = fields.Float('Actual planned quantity', readonly=True)
    planned_quantity = fields.Float('Planned quantity')
    planned_panel_quantity = fields.Integer('Planned Panel quantity', readonly=True)
    planned_set_quantity = fields.Integer(string='Planned SET Quantity', readonly=True)
    planned_sheet_quantity = fields.Integer(string='Planned SHEET Quantity', readonly=True)

    sheet_panel = fields.Float(string='SHEET/PANEL', readonly=True)
    panel_pcs_ratio = fields.Float(string='PANEL PCS ratio', readonly=True)
    panel_area = fields.Float(string='PANEL Area(m2)', readonly=True)
    panel_height = fields.Float(string='panel height', readonly=True)
    panel_width = fields.Float(string='panel width', readonly=True)
    sheet_height = fields.Float(string='sheet height', readonly=True)
    sheet_width = fields.Float(string='sheet width', readonly=True)
    pcs_total = fields.Float(string='pcs total', readonly=True)
    panel_qty = fields.Float(string='panel qty', readonly=True)
    uom_set_qty = fields.Float(string='set qty', readonly=True)

    inventory_qty = fields.Float(string='Inventory Quantity')

    line_ids = fields.One2many('plan.material.requirement.line.wizard', 'order_id', string='Material details line')

    split_line_ids = fields.One2many('plan.material.requirement.split.line.wizard', 'order_id')
    split_count = fields.Integer('Count', compute='_compute_split_count', readonly=True, store=True)

    inspection_stock = fields.Boolean('Inspection Stock', default=True)

    def _compute_inventory_qty(self):
        for rec in self:
            rec.inventory_qty = rec.product_id.compute_version_qty_available()

    @api.depends('split_line_ids')
    def _compute_split_count(self):
        for rec in self:
            rec.split_count = len(rec.split_line_ids)

    @api.depends('development_ids_str')
    def _compute_development_ids(self):
        for rec in self:
            if not rec.development_ids_str:
                continue
            id_list = rec.development_ids_str.split(',')
            rec.development_ids = [(4, int(i)) for i in id_list]

    @api.depends('sale_order_line_ids_str')
    def _compute_sale_ids(self):
        for rec in self:
            if not rec.sale_order_line_ids_str:
                continue
            id_list = rec.sale_order_line_ids_str.split(',')
            rec.sale_order_line_ids = [(4, int(i)) for i in id_list]

    def _get_bom_line_ids(self, bom, product_id, uom_id, qty):
        bom_lines = []

        # 创建顶层产品的 BOM 行
        # bom_lines.append((0, 0, {
        #     'product_id': product_id.id,
        #     'qty': qty,
        #     'level': 1,
        #     'uom_id': uom_id.id,
        # }))
        for bom_line in bom.bom_line_ids:
            bom_lines.append((0, 0, {
                'product_id': bom_line.product_id.id,
                'qty': bom_line.product_qty * qty,
                'level': 1,
                'uom_id': bom_line.product_uom_id.id,
            }))
            # 递归处理子 BOM
            if bom_line.product_id.bom_ids:
                bom_lines += self._process_bom(bom_line.product_id.bom_ids[0], bom_line.product_qty * qty, 2)

        return bom_lines

    def get_replace_product_ids(self, sale_line_ids, product_id, m_product_id):
        """
        product_id  成品
        m_product_id 物料
        """
        replace_ids = self.env['s.mrp.bom.product.replace'].search([
            ('sale_id', 'in', sale_line_ids.mapped('order_id').ids),
            ('origin_product_id', '=', m_product_id.id)
        ], limit=1)
        if not replace_ids:
            s_product_ids = self.env['product.product'].search([
                ('categ_id.code', '=', 'CHP'),
                ('serial_number', '=', product_id.serial_number)
            ])
            replace_ids = self.env['s.mrp.bom.product.replace'].search([
                ('finished_product', 'in', s_product_ids.ids),
                ('origin_product_id', '=', m_product_id.id)
            ], limit=1)
        return replace_ids

    def _get_line_vals_list(self, bom, qty, sale_line_ids):
        line_vals_list = []
        material_vals = {}
        material_vals = self._get_bom_material_total(bom, material_vals)

        if material_vals:
            for product_id, value in material_vals.items():
                if sale_line_ids:
                    replace_product_id = self.get_replace_product_ids(sale_line_ids, sale_line_ids[0].product_id, product_id)
                else:
                    replace_product_id = False
                line_vals_list.append({
                    'product_id': product_id.id,
                    'qty': qty * value,
                    'panel_qty': qty * value,
                    'base_qty': value,
                    'replace_product_id': replace_product_id.replace_product_id if replace_product_id else False
                })
        return line_vals_list

    def _compute_scrap_rate(self, product_id):
        record = self.env['s.mrp.base'].search([('product_id', '=', product_id.id)], limit=1, order='scrap_ratio')
        if record:
            return record.scrap_ratio * 100
        else:
            return 0

    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        active_ids = self._context.get('active_ids', [])
        active_model = self._context.get('active_model', '')

        bom = self.env['mrp.bom']
        product_id = self.env['product.product']
        quantity = 0
        sale_line_ids = self.env['sale.order.line']
        if active_model == 'sale.order.line':
            line_ids = self.env['sale.order.line'].browse(active_ids)
            sale_line_ids |= line_ids
            line = line_ids[0]

            if not line.product_id.bom_ids:
                raise UserError(_('Product %s related BOM does not exist!', line.product_id.name))

            bom = line.product_id.bom_ids[0]
            product_id = line.product_id
            quantity = sum(line_ids.mapped('s_remaining_planned_qty'))

            res.update({
                # 'sale_id': line.order_id.id,
                'sale_order_line': line.id,
                'sale_order_line_ids_str': ','.join([str(line.id) for line in line_ids]),
                # 'sale_ids': [(6, 0, line_ids.mapped('order_id').ids)],
                'sale_order_line_ids': [(6, 0, line_ids.ids)],
            })

        elif active_model == 's.research.and.development.order.line':
            development_ids = self.env['s.research.and.development.order.line'].browse(active_ids)
            development = development_ids[0]
            bom = development.product_id.bom_ids[0]
            product_id = development.product_id
            quantity = sum(development_ids.mapped('remaining_planned_qty'))

            res.update({
                'development_id': development.id,
                'development_ids_str': ','.join([str(line.id) for line in development_ids]),
                'development_ids': [(6, 0, development_ids.ids)],
            })

        if all([bom, product_id]):
            if product_id.detailed_type != 'product':
                return res

            bom_lines = self._get_bom_line_ids(bom, product_id, product_id.uom_id, quantity)

            line_vals_list = []
            if product_id.makeup_product_ids:
                makeup_setting_vals_list = []
                temporary_line_vals = {}
                sheet_qty = 0
                pcs_total = 0
                uom_set_qty = 0
                sheet_height = 0
                sheet_width = 0
                panel_total_area = 0
                all_product = product_id.makeup_product_ids.mapped('product_id') + product_id
                all_pcs = 0
                for p in all_product:
                    p_panel_height, p_panel_width, p_panel_qty, p_set_qty, p_sheet_qty, p_sheet_height, p_sheet_width = self._get_panel_width_height(p)
                    all_pcs += p_sheet_qty * p_panel_qty

                for s_product in all_product:
                    if not s_product.bom_ids:
                        raise UserError(f'The product {s_product.name} is not configured with a BOM!')
                    line_bom = s_product.bom_ids[0]
                    line_panel_height, line_panel_width, line_panel_qty, line_set_qty, line_sheet_qty, line_sheet_height, line_sheet_width = self._get_panel_width_height(s_product)
                    ratio = line_sheet_qty * line_panel_qty / all_pcs
                    sheet_qty += line_sheet_qty
                    pcs_total += line_sheet_qty * line_panel_qty
                    uom_set_qty += line_sheet_qty * (line_panel_qty / line_set_qty)
                    sheet_height = line_sheet_height
                    sheet_width = line_sheet_width
                    panel_total_area += (line_panel_height * line_panel_width * line_sheet_qty)

                    makeup_setting_vals_list.append((0, 0, {
                        'product_id': s_product.id,
                        'ratio': ratio,
                        'base_panel_qty': line_panel_qty
                    }))
                    for vals in self._get_line_vals_list(line_bom, quantity * ratio, sale_line_ids):
                        line_product_id = vals.pop('product_id')
                        if s_product in temporary_line_vals:
                            temporary_line_vals[line_product_id]['qty'] += vals['qty']
                            temporary_line_vals[line_product_id]['panel_qty'] += vals['panel_qty']
                            temporary_line_vals[line_product_id]['base_qty'] += vals['base_qty']
                        else:
                            temporary_line_vals[line_product_id] = vals
                panel_qty = pcs_total / sheet_qty
                for key, value in temporary_line_vals.items():
                    value.update({'product_id': key})
                    line_vals_list.append((0, 0, value))

                # utilization_rate = panel_total_area / (sheet_height * sheet_width)
                res['split_line_ids'] = makeup_setting_vals_list

            else:
                temporary_line_vals_list = self._get_line_vals_list(bom, quantity, sale_line_ids)
                for vals in temporary_line_vals_list:
                    line_vals_list.append((0, 0, vals))

                panel_height, panel_width, panel_qty, uom_set_qty, sheet_qty, sheet_height, sheet_width = self._get_panel_width_height(product_id)
                pcs_total = panel_qty * sheet_qty

                # 计算利用率  PANEL总面积 / SHEET总面积
                # utilization_rate = (panel_height * panel_width * sheet_qty) / (sheet_height * sheet_width)

            res.update({
                'product_id': product_id.id,
                'remaining_planned_quantity': quantity,
                'planned_quantity': quantity,
                'panel_pcs_ratio': bom.panel_pcs_ratio,
                'bom_line_ids': bom_lines,
                'pcs_total': pcs_total,
                'panel_qty': panel_qty,
                'uom_set_qty': uom_set_qty,
                'bom_id': bom.id,
                'line_ids': line_vals_list,
                'opt_lot_size': product_id.opt_lot_size or 72,
                'scrap_rate': product_id.scrap_rate,
                'sheet_panel': sheet_qty,
                'sheet_width': sheet_width,
                'sheet_height': sheet_height,
                'inventory_qty': product_id.compute_version_qty_available(),
                'scrap_rate': self._compute_scrap_rate(product_id)
                # 'utilization_rate': utilization_rate
            })
        return res

    @api.onchange('planned_quantity', 'scrap_rate')
    def onchange_qty(self):
        if self.remaining_planned_quantity < self.planned_quantity:
            return {
                'warning': {
                    'title': _("Warning"),
                    'message': _('The planned quantity is greater than the planned quantity!')
                }
            }
        if self.panel_qty == 0:
            self.panel_area = 0
        else:
            # if self.split_count > 0:
                # 合拼的用大板算
            self.actual_planned_quantity = math.ceil((self.planned_quantity * (1 + (self.scrap_rate / 100))) / (self.pcs_total)) * (self.pcs_total)
            self.planned_sheet_quantity = self.actual_planned_quantity / self.pcs_total
            self.planned_panel_quantity = self.planned_sheet_quantity * self.sheet_panel
            self.panel_area = self.sheet_height * self.sheet_width * self.planned_sheet_quantity / 1000000
            self.planned_set_quantity = self.actual_planned_quantity / self.uom_set_qty

            # else:
            #     # 实际要投料的数量 计算公式  向上取整(投料数量 * (1 + 报废率) / 拼板数) * 拼版数
            #     self.actual_planned_quantity = math.ceil((self.planned_quantity * (1 + (self.scrap_rate / 100))) / self.panel_qty) * self.panel_qty
            #     self.panel_area = self.panel_height * self.panel_width * (self.actual_planned_quantity / self.panel_qty) / 1000000  # 板的面积
            #     self.planned_panel_quantity = math.ceil(self.actual_planned_quantity / self.panel_qty)  # 投料  板  的数量
            #     self.planned_set_quantity = math.ceil(self.actual_planned_quantity / self.set_qty) if self.set_qty else 0  # 投料  SET  的数量

        # 更新物料明细汇总
        for line in self.line_ids:
            # 板材考虑利用率  /////   2025-02-01 更新  全部按大板投料，无需考虑利用率
            if line.product_id.categ_id.parent_id.code and line.product_id.categ_id.parent_id.code == 'CR':
                line.panel_qty = self.planned_sheet_quantity * line.base_qty
                # if self.split_count > 0:
                #     line.panel_qty = self.planned_sheet_quantity
                # else:
                #     # line.panel_qty = self.actual_planned_quantity * line.base_qty / self.utilization_rate
                #     line.panel_qty = self.actual_planned_quantity * line.base_qty
            else:
                line.panel_qty = self.actual_planned_quantity * line.base_qty

        # 更新拆分数据
        for line in self.split_line_ids:
            line.qty = self.actual_planned_quantity * line.ratio
            line.panel_qty = line.qty / line.base_panel_qty

        # 更新物料清单行
        self.bom_line_ids = [(5, 0, 0)]
        bom_lines = self._get_bom_line_ids(self.bom_id, self.product_id, self.product_id.uom_id, self.actual_planned_quantity)
        self.bom_line_ids = bom_lines

    def action_plan_material_requirement(self):

        if self.sale_order_line_ids.filtered(lambda x: x.state != 'sale'):
            raise UserError(_('This sales order is not ready to process, please check it again.'))

        if self.remaining_planned_quantity < self.planned_quantity:
            raise UserError(_('The planned quantity is greater than the planned quantity!'))
            #  注释说明 瞬态模型的 最佳批量大小 和 报废率 不再更新产品模板上的 最佳批量大小 和 报废率
            # for item in self:
            #     if item.sale_id and item.sale_id.order_line:
            #         item.sale_id.order_line[0].product_template_id.opt_lot_size = item.opt_lot_size
            #         item.sale_id.order_line[0].product_template_id.scrap_rate = item.scrap_rate
            # self.sale_order_line.action_replan(self.remaining_planned_quantity)

        # 创建生产单
        production_ids = self.create_production()

        for sale_line in self.sale_order_line_ids:
            sale_line.s_mrp_production_ids = [(4, p.id) for p in production_ids]

        for development_line in self.development_ids:
            development_line.mrp_production_ids = [(4, p.id) for p in production_ids]

        material_picking_id = self.create_picking(production_ids)
        # if material_picking_id:
        #     production_ids.write({'s_material_picking_ids': [(4, material_picking_id.id)]})

        production_ids.action_confirm()
        all_production = self.env['mrp.production']
        for p in production_ids:
            all_production |= p._get_all_child()
        all_production |= production_ids
        if material_picking_id:
            all_production.write({'s_material_picking_ids': [(4, material_picking_id.id)]})
        # 更新板材用量
        # all_production._compute_move_raw_ids_cr()
        all_move_raw_ids = all_production.mapped('move_raw_ids')
        for r_line in self.line_ids.filtered(lambda x: x.replace_product_id):
            material_picking_id.move_ids.filtered(lambda x: x.product_id == r_line.product_id).write({
                'product_id': r_line.replace_product_id.id,
                'product_uom': r_line.replace_product_id.uom_id.id,
                'description_picking': r_line.replace_product_id.name,
                'name': r_line.replace_product_id.name
            })
            all_move_raw_ids.filtered(lambda x: x.product_id == r_line.product_id).write({
                'product_id': r_line.replace_product_id.id,
                'product_uom': r_line.replace_product_id.uom_id.id,
                'description_picking': r_line.replace_product_id.name,
                'name': r_line.replace_product_id.name
            })

        material_picking_id.move_ids._do_unreserve()

        # material_picking_id.action_assign()
        # for move in material_picking_id.move_ids:
        #     product = move.product_id
        #     if product.categ_id.parent_id and product.categ_id.parent_id.code and product.categ_id.parent_id.code == 'CR':
                # 绑定板材发料库存移动和生产库存移动
                # move.write({'move_dest_ids': [(4, move.id) for move in all_move_raw_ids.filtered(lambda x: x.product_id == product)]})

        for line in self.line_ids:
            product_id = line.replace_product_id or line.product_id
            # if product_id.categ_id.parent_id.code and product_id.categ_id.parent_id.code == 'CR':
            #     continue
            product_lock_ids = self.env['s.product.lock'].sudo().search([
                ('sale_order_line_id', 'in', self.sale_order_line_ids.ids),
                # ('product_id', '=', product_id.id),
                ('active', 'in', (True, False))
            ])
            product_lock_ids.write({'x_delete': True})
            product_lock_ids.sudo().action_archive()
            if self.inspection_stock:
                # move_ids = self.env['stock.move'].search([
                #     ('product_id', '=', product_id.id),
                #     ('location_dest_id.usage', '=', 'production'),
                #     ('state', 'not in', ('done', 'cancel'))
                # ])
                # move_qty = sum(move_ids.mapped('product_qty'))

                if float_compare(product_id.qty_available - product_id.s_lock_stock_qty, product_id.outgoing_qty, precision_rounding=product_id.uom_id.rounding) == -1:
                    raise ValidationError(_('Product (%s), Inventory is not enough for production, material feeding is not allowed', product_id.name))
        # self.sale_id._compute_with_production()
        material_picking_id.action_confirm()

        # A/B拼版分摊板料用量
        if self.split_count > 0:
            material_usage = {}
            for move in material_picking_id.move_ids:
                material_usage[move.product_id] = move.product_uom_qty

            for product_id, g in groupby(all_move_raw_ids.sorted(key=lambda x: x.product_id.id), key=lambda x: x.product_id):
                product_move_ids = self.env['stock.move'].concat(*g)
                if product_id not in material_usage:
                    continue

                total = material_usage[product_id]
                move_total = sum(product_move_ids.mapped('product_uom_qty'))
                rounding = product_move_ids[0].product_uom.rounding
                contrast = float_compare(total, move_total, precision_rounding=rounding)
                if contrast != 0:
                    for move in product_move_ids:
                        move.product_uom_qty = float_round(total * (move.product_uom_qty / move_total), precision_rounding=move.product_uom.rounding)
                    difference = total - sum(product_move_ids.mapped('product_uom_qty'))
                    # 分配后的有小数差异，将差值放到最后一个生产单上
                    if not float_is_zero(difference, precision_rounding=rounding):
                        product_move_ids[-1].product_uom_qty += difference

        # 新用户标签制做通知
        self._new_partner_label_message_post()
        return {
            'name': _('Production Order'),
            'type': 'ir.actions.act_window',
            'res_model': 'mrp.production',
            'view_mode': 'tree,form',
            'target': 'current',
            'views': [[False, 'tree'], [False, 'form']],
            'context': {'create': False},
            'domain': [('id', 'in', all_production.ids)],
        }

    def create_picking(self, production_ids):
        """
            创建板材的合并领料单
        """
        if not production_ids:
            return

        all_production_ids = self.env['mrp.production']
        if production_ids.mapped('s_sale_order_line_ids'):
            sale_line_ids = production_ids.mapped('s_sale_order_line_ids')
            sale_ids = sale_line_ids.mapped('order_id')
            all_production_ids = sale_ids.mapped('order_line.s_mrp_production_ids')
        elif production_ids.mapped('s_development_line_ids'):
            development_line_ids = production_ids.mapped('s_development_line_ids')
            all_production_ids = development_line_ids.mapped('mrp_production_ids')

        all_production_ids = all_production_ids.filtered(lambda x: x.state != 'cancel')
        if not all_production_ids:
            return

        material_picking_id = self.env['stock.picking']
        material_picking_ids = all_production_ids.mapped('picking_ids').filtered(lambda x: x.state == 'draft' and x.s_material_order)
        if not material_picking_ids:
            picking_type = self.env['stock.picking.type'].search([('code', '=', 'internal'), ('barcode', '=', 'WTT-CB')], limit=1)
            use_station = self.env['work.station'].search([('code', '=', 'CUT')], limit=1)

            move_vals_list = []
            for line in self.line_ids:
                product = line.product_id
                if product.categ_id.parent_id and product.categ_id.parent_id.code and product.categ_id.parent_id.code == 'CR':
                    vals = {
                        'company_id': self.env.user.company_id.id,
                        'date': datetime.datetime.now(),
                        'location_id': picking_type.default_location_src_id.id,
                        'location_dest_id': picking_type.default_location_dest_id.id,
                        'product_id': line.product_id.id,
                        'name': line.product_id.display_name,
                        'procure_method': 'make_to_stock',
                        'product_uom': line.product_id.uom_id.id,
                        'product_before_rounding_qty': line.panel_qty,
                        'product_uom_qty': math.ceil(line.panel_qty),
                    }
                    move_vals_list.append((0, 0, vals))

            if move_vals_list:
                vals = {
                    'company_id': self.env.user.company_id.id,
                    'location_id': picking_type.default_location_src_id.id,
                    'location_dest_id': picking_type.default_location_dest_id.id,
                    'move_type': 'direct',
                    'picking_type_id': picking_type.id,
                    's_material_order': True,
                    'work_station_id': use_station.id,
                    'move_ids': move_vals_list,
                    'origin': ','.join(list(set(production_ids.mapped('base_mo')))) + ' / ' + self.product_id.name
                    # 'group_id': production_ids[0].procurement_group_id.id
                }
                material_picking_id = self.env['stock.picking'].create(vals)

        else:
            picking = material_picking_ids[0]
            material_picking_id = picking
            for line in self.line_ids:
                product = line.product_id
                move = picking.move_ids.filtered(lambda x: x.product_id == product)
                if product.categ_id.parent_id and product.categ_id.parent_id.code and product.categ_id.parent_id.code == 'CR':
                    if move:
                        move.product_before_rounding_qty += line.qty
                        move.product_uom_qty = math.ceil(move.product_before_rounding_qty)
                        # else:
                        #     move.product_uom_qty += line.qty
                    else:

                        vals = {
                            'company_id': self.env.user.company_id.id,
                            'date': datetime.datetime.now(),
                            'location_id': picking.location_id.id,
                            'location_dest_id': picking.location_dest_id.id,
                            'product_id': line.product_id.id,
                            'name': line.product_id.display_name,
                            'procure_method': 'make_to_stock',
                            'product_uom': line.product_id.uom_id.id,
                            'product_before_rounding_qty': line.panel_qty,
                            'product_uom_qty': math.ceil(line.panel_qty),
                        }

                        picking.write({'move_ids': [(0, 0, vals)]})

        return material_picking_id

    def _create_production(self, product_id, planned_panel_quantity, planned_quantity, uom_panel):
        """
        生成生产单
        @param product_id: 产品
        @param planned_panel_quantity: 计划投料张数
        @param planned_quantity: 计划投料的数量
        @param uom_panel: 单位 pnl 数量
        """
        self.ensure_one()
        vals_list = []
        picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1)
        base_mo = None
        while planned_panel_quantity > 0 and planned_quantity > 0:
            if self.opt_lot_size == 0:
                quantity = planned_panel_quantity * uom_panel
                planned_panel_quantity = 0
            else:
                lot_size = min(self.opt_lot_size, planned_panel_quantity)
                # 尾卡小于等于10张，合并为一张
                if planned_panel_quantity - lot_size <= 10:
                    lot_size = planned_panel_quantity
                planned_panel_quantity -= lot_size
                quantity = lot_size * uom_panel

            if quantity >= planned_quantity:
                quantity = planned_quantity
                planned_quantity = 0
            else:
                planned_quantity -= quantity

            vals = {
                'consumption': 'strict',
                'date_start': datetime.datetime.now(),
                'mo_type': 'N',
                'product_qty': quantity,
                'picking_type_id': picking_type.id,
                'location_dest_id': picking_type.default_location_dest_id.id,
                'location_src_id': picking_type.default_location_src_id.id,
                'base_mo': base_mo,
            }
            if self.sale_order_line:
                order_type = self.sale_order_line.order_id.order_type
                if order_type == 'new':
                    order_type = 'N'
                elif order_type == 'repeat':
                    order_type = 'R'
                elif order_type == 'change':
                    order_type = 'C'
                else:
                    order_type = 'N'
                vals.update({
                    'customer': self.sale_order_line.order_id.partner_id.id,
                    'company_id': self.sale_order_line.order_id.company_id.id,
                    'product_id': product_id.id,
                    'product_uom_id': product_id.uom_id.id,
                    'origin': ','.join(self.sale_order_line_ids.mapped('order_id.name')),
                    's_order_type': order_type
                })
            elif self.development_id:
                vals.update({
                    'company_id': self.development_id.order_id.company_id.id,
                    'product_id': product_id.id,
                    'product_uom_id': product_id.uom_id.id,
                    'origin': ','.join(self.development_ids.mapped('order_id.name')),
                    's_order_type': self.development_id.order_id.order_type,
                })

            # A/B拼版标记订单
            if self.split_count > 0:
                vals.update({
                    's_makeup_order': True,
                    # 's_makeup_product_id': self.product_id.id
                })
            vals_list.append(vals)
        production_ids = self.env['mrp.production'].create(vals_list)
        # if not base_mo:
        #     base_mo = new_production.base_mo
        return production_ids

    def create_production(self):

        production_ids = self.env['mrp.production']
        # 判断是否有拼版
        if self.split_count > 0:
            for line in self.split_line_ids:
                line_planned_panel_quantity = line.panel_qty
                line_planned_quantity = line.qty
                production_ids |= self._create_production(line.product_id, line_planned_panel_quantity, line_planned_quantity, line.base_panel_qty)


        else:
            planned_panel_quantity = self.planned_panel_quantity
            planned_quantity = self.actual_planned_quantity
            production_ids |= self._create_production(self.product_id, planned_panel_quantity, planned_quantity, self.panel_qty)

        return production_ids

    def _new_partner_label_message_post(self):
        partner_ids = self.sale_order_line_ids.mapped('order_id.partner_id')
        for partner in partner_ids:
            if self.env['mrp.production'].search_count([('customer', '=', partner.id)]) == 0:
                message = Markup("""
                    有新客户 (%s), 请及时制作出货标签 <br />
                    New customer (%s), please make shipping labels in a timely manner
                """ % (partner.name, partner.name))
                self.env['mail.message'].post_message_to_group_user('pcbmanufact.group_s_new_partner_massage', message)

    def _process_bom(self, bom, qty, level=1):
        """
        递归处理 BOM 和子 BOM，计算数量并创建 BOM 行。
        """
        # 用来存储要创建的 BOM 行
        bom_lines = []

        for x in bom.bom_line_ids:
            # 检查是否已经存在相同的 BOM 行，避免重复插入
            existing_bom_line = self.env['requirement.bom.line.wizard'].search([
                ('plan_id', '=', self.id),
                ('product_id', '=', x.product_id.id),
                ('level', '=', level)
            ])
            if not existing_bom_line:
                bom_lines.append((0, 0, {
                    'plan_id': self.id,
                    'product_id': x.product_id.id,
                    'qty': x.product_qty * qty,
                    'level': level,
                    'uom_id': x.product_uom_id.id,
                }))

            # 如果该产品还有 BOM，则递归处理子 BOM
            if x.product_id.bom_ids:
                bom_lines += self._process_bom(x.product_id.bom_ids[0], x.product_qty * qty, level + 1)

        # 返回新的 BOM 行数据
        return bom_lines

    def _get_bom_material_total(self, bom, vals):
        if not vals:
            vals = {}

        for line in bom.bom_line_ids:
            if line.child_bom_id:
                vals = self._get_bom_material_total(line.child_bom_id, vals)
            else:
                if line.product_id in vals:
                    # 芯板材料不根据物料清单中的用量计算
                    if line.product_id.categ_id.parent_id.code and line.product_id.categ_id.parent_id.code == 'CR':
                        vals[line.product_id] += 1
                    else:
                        vals[line.product_id] += line.product_qty
                else:
                    if line.product_id.categ_id.parent_id.code and line.product_id.categ_id.parent_id.code == 'CR':
                        vals[line.product_id] = 1
                    else:
                        vals[line.product_id] = line.product_qty
        return vals

    def _get_panel_width_height(self, product_id):
        uom_values = {x.uom_id.code: x.uom_value for x in product_id.uom_value_ids}
        panel_height = uom_values.get('PNLL', 0)
        panel_width = uom_values.get('PNLW', 0)
        panel_qty = uom_values.get('PANEL', 0)
        set_qty = uom_values.get('SET', 0)
        sheet_qty = uom_values.get('SHEET', 0)
        sheet_height = uom_values.get('SHETL', 0)
        sheet_width = uom_values.get('SHETW', 0)
        return panel_height, panel_width, panel_qty, set_qty, sheet_qty, sheet_height, sheet_width

    def go_back(self):
        return self.env["ir.actions.actions"]._for_xml_id("pcbmanufact.action_sale_order_line_select_to_wizard")


class PlanMaterialRequirementLineWizard(models.TransientModel):
    _name = 'plan.material.requirement.line.wizard'
    _description = 'Material requirements Lines'

    order_id = fields.Many2one('plan.material.requirement.wizard')
    product_id = fields.Many2one('product.product', 'Product')
    product_name = fields.Char('Product Name', related='product_id.name')
    default_code = fields.Char('Code', related='product_id.default_code')
    category_id = fields.Many2one('product.category', related='product_id.categ_id', string='Product Category')
    base_qty = fields.Float('Base Quantity', digits=(10, 6))
    qty = fields.Float('Quantity', digits='Product Unit of Measure')
    uom_id = fields.Many2one('uom.uom', string='Uom', related='product_id.uom_id')
    # panel_area = fields.Float(string='PANEL Area(m2)')
    panel_qty = fields.Float('Quantity', digits='Product Unit of Measure')

    stock_qty = fields.Float('Stock Quantity', digits='Product Unit of Measure', compute='_compute_qty')
    prenatal_qty = fields.Float('Prenatal Quantity', digits='Product Unit of Measure', compute='_compute_qty')
    production_qty = fields.Float('Production Quantity', digits='Product Unit of Measure', compute='_compute_qty')
    # panel_height = fields.Float(string='PANEL Height')
    replace_product_id = fields.Many2one('product.product', 'replace product')

    order_lock_stock_qty = fields.Float('order lock stock qty', digits='Product Unit of Measure', compute='_compute_qty')
    free_qty = fields.Float('free qty', compute='_compute_qty')
    gap_qty = fields.Float('gap qty', digits='Product Unit of Measure', compute='_compute_gap_qty')
    # panel_width = fields.Float(string='PANEL Width')
    # pcs_panel_ratio = fields.Float(string='pcs panel ratio')

    @api.depends('product_id', 'replace_product_id')
    def _compute_qty(self):
        for rec in self:
            product_id = rec.replace_product_id or rec.product_id
            rec.stock_qty = product_id.s_actual_quantity
            rec.prenatal_qty = product_id.pre_production_qty
            rec.production_qty = product_id.pre_production_count
            rec.free_qty = product_id.s_free_qty
            product_lock_ids = self.env['s.product.lock'].search([
                ('lock_type', '=', 'stock'),
                ('product_id', '=', product_id.id),
                ('sale_order_line_id', 'in', rec.order_id.sale_order_line_ids.ids)
            ])
            if product_lock_ids:
                rec.order_lock_stock_qty = sum(product_lock_ids.mapped('qty'))
            else:
                rec.order_lock_stock_qty = 0

    @api.depends('replace_product_id', 'panel_qty')
    def _compute_gap_qty(self):
        for rec in self:
            if float_compare(rec.order_lock_stock_qty + rec.free_qty, rec.panel_qty, precision_rounding=rec.product_id.uom_id.rounding) == -1:
                rec.gap_qty = rec.panel_qty - rec.order_lock_stock_qty - max(rec.free_qty, 0)
            else:
                rec.gap_qty = 0


class PlanMaterialRequirementSplitLineWizard(models.TransientModel):
    _name = 'plan.material.requirement.split.line.wizard'
    _description = 'Material requirements split Lines'

    order_id = fields.Many2one('plan.material.requirement.wizard')
    product_id = fields.Many2one('product.product', 'Product')
    product_name = fields.Char('Product Name', related='product_id.name')
    default_code = fields.Char('Code', related='product_id.default_code')
    qty = fields.Integer('Quantity')
    panel_qty = fields.Integer('PANEL Quantity')
    base_panel_qty = fields.Integer('Base PANEL Quantity')
    ratio = fields.Float('Ratio')


class RequirementBomLine(models.TransientModel):
    _name = 'requirement.bom.line.wizard'
    _description = 'Requirement BOM Line Wizard'

    plan_id = fields.Many2one('plan.material.requirement.wizard', 'Plan')
    product_id = fields.Many2one('product.product', 'Product')
    default_code = fields.Char('Product Code', compute='get_product_info', store=True)
    qty = fields.Float('Quantity', default=1.0)
    uom_id = fields.Many2one('uom.uom', 'Unit of Measure')
    level = fields.Integer('Level', default=1)  # BOM 层级字段
    qty_available = fields.Float('Quantity On Hand', related='product_id.s_actual_quantity', store=True)
    before_production = fields.Float('Before production', related='product_id.pre_production_qty')
    free_qty = fields.Float('Free To Use Quantity', related='product_id.free_qty', store=True)
    panel_length_qty = fields.Float(string='Panel Length Qty', compute='get_product_info', store=True)
    panel_width_qty = fields.Float(string='Panel Width Qty', compute='get_product_info', store=True)
    description = fields.Html('Description', related='product_id.description')
    name = fields.Char(string='Name', compute='_compute_bom_level_name', store=True)
    product_type = fields.Char(string='Product Category', compute='_compute_bom_product_type')

    @api.depends('product_id')
    def _compute_bom_product_type(self):
        for item in self:  # 找到最上级类别
            category = item.product_id.categ_id
            while category.parent_id:
                category = category.parent_id
            item.product_type = category.name

    @api.depends('product_id', 'level', 'default_code')
    def _compute_bom_level_name(self):
        for item in self:
            level_indent = '——' * max(0, item.level - 1)
            item.name = f"{level_indent}{item.product_id.default_code  or ''} {item.product_id.name or ''}"

    @api.depends('product_id')
    def get_product_info(self):
        for item in self:
            panel_length_uom_id = self.env['uom.uom'].search([('name', '=', 'Panel Length')])
            panel_width_uom_id = self.env['uom.uom'].search([('name', '=', 'Panel Width')])

            domain = [('product_id', '=', item.product_id.product_tmpl_id.id)]

            panel_length_value_id = self.env['product.uom.value'].search(domain + [('uom_id', '=', panel_length_uom_id.id)])
            panel_width_value_id = self.env['product.uom.value'].search(domain + [('uom_id', '=', panel_width_uom_id.id)])

            item.default_code = item.product_id.product_tmpl_id.default_code
            item.panel_length_qty = panel_length_value_id.uom_value
            item.panel_width_qty = panel_width_value_id.uom_value
